Part III
MFC and COM Programming

In This Part

  COM 373
  COM and MFC 409
  MFC OLE Servers 449
  MFC OLE Clients 485
  MFC ActiveX Controls 519
  MFC ActiveX Control Containers 561
  Using MFC and ATL 589
  Scripting Your MFC Application 625

Chapter 10
COM

by K. David White

In This Chapter

  A Little History 374
  Interfaces, Objects, and Methods 375
  Servers, Clients, and Classes 382
  The COM Runtime Environment 384
  Marshaling and Threading 386
  COM, OLE, and Automation 390
  Persisting COM Data 393
  Identifying COM Data (Monikers) 396
  Transferring Data 397
  DCOM 399
  Some Important Information 405
  Further Reading 407

ActiveX and OLE seem to be in the forefront of application development news these days. These technologies, however, are driven from a base specification referred to as Component Object Model (COM). COM, COM, everywhere COM! It appears that everything is becoming active. From desktop applications working with compound documents to the current desktop, you find this technology as a major focus of Microsoft’s direction. Object-oriented programming has been around for some time now, but COM still can’t simply be defined as another implementation. It is much more than that.

The COM specification describes a methodology for implementing compon ents: reusable objects that can be attached in different ways to create different applications. Obviously, if you are reading this book, you are familiar with the C++ representation of object-oriented programming. In the interest of “doing it better,” you are always looking for better and more efficient ways to solve software development problems. One such approach is reuse.

Reuse has been a hot topic for some time. Think of a manufacturing process that simply takes parts out of inventory, moves them to stations along the manufacturing line, and then assembles them in order, making a usable product. The software industry’s desire for a similar manufacturing paradigm also provides the thrust behind a reusable solution to software development. As hardware technology has progressed, so has the process of developing software. I could ramble on and on about the benefits and disadvantages of object reuse, but let’s take an inside look at this amazingly simple yet paradoxically complex method of reusability.

A Little History

Long ago and far away (okay, so it’s Redmond and was only a few years ago), there was OLE 1. Object linking and embedding was developed primarily to support compound documents. OLE provides an application with the capability to contain and manipulate a document that was produced by another application as if it were its own. Thus a compound document could give the appearance of a single entity. This integration provided a large boost to the productivity of the users of these applications. No longer did a user have to use two applications to work with a single set of information; to the user it appeared as if only one application was performing the work.

Then along came OLE 2, which was a marked improvement not only in functionality, but also in performance. OLE 1 was simply a methodology to solve a unique problem, but it didn’t go far enough in solving the bigger problem of reusable objects. OLE 2 was aimed at creating a way for objects to communicate with each other in a consistent and reliable manner.

Microsoft decided to drop OLE as an abbreviation, as well as any future version numbers, and to simply call the technology OLE. No longer was OLE to represent object linking and embedding and compound documents, but was used to represent any application that was COM-based. Well, Microsoft wasn’t finished with its marketing terminology “switcharoo.” In 1996, ActiveX was born, or was it? Was this another name for OLE/COM, or was this something entirely different? It appeared that ActiveX was used to refer to Internet technologies, although in fact ActiveX replaced the former VBX components. 16-bit Visual Basic had a mechanism for incorporating extension “controls,” which was well-known but not formally specified. ActiveX changed that while at the same time providing Microsoft with a portable Internet control methodology. ActiveX was, in fact, a derivative technology based on a central core—COM. Today, other technologies have followed a similar development theme, such as DirectX (see Chapter 30, “MFC and DirectX”).

Because COM terminology was so interchangeable, OLE now refers only to object linking and embedding. ActiveX refers mainly to visual components used in developing user-interface extensions to applications or Web pages. But the underlying technology that binds all this terminology is COM.

Interfaces, Objects, and Methods

COM provides the capability for one subset of an application to communicate not only with another subset of the same application, but also with a subset of a different application. This interchangeability is the goal of object-oriented application development: Creating reusable components that can not only be “plugged in” by applications to perform work, but that can also communicate with other components is exactly what COM has accomplished. COM does this by supporting one or more interfaces that other applications and objects can use to access the component’s internal methods.

Some Terminology

If you’re new to COM, some of the terminology might be a little confusing. Hopefully, you are not new to object-oriented terminology. In the following sections, I will explain some of the terms I will be using to define this technology.

Interfaces

An interface is defined as an exposed connection for a controlling application to access a COM object. Here’s what a COM interface is not:

  Interfaces are not classes. It is common to assume that an interface is a class definition. This mistake is commonly made because of interface inheritance. The interface has no implementation—it is simply a window into the COM object, which is defined by a class. (If you are totally confused, I can clear it up in a minute.)
  An interface is not a COM object. When I first started working with COM, I was confusing an interface with the COM object itself. An interface is a collection of functions and is the exposing mechanism of the object itself.

There can be multiple interfaces for one COM object. In fact, COM objects normally do support more than one interface.

Interfaces are immutable. The COM specification dictates that COM interfaces cannot be versioned. Interface version conflicts are avoided by creating entirely new interfaces for even the most minor modification to the original interface.



Objects

A COM object, also commonly referred to as a COM component, is a particular instance of a COM class. It can contain many functions accessible through its interfaces. A COM object must have at least the IUnknown interface, which I will discuss later in this chapter.

Classes

A COM component is a specific instance of a COM class, which defines the component’s interfaces and methods. Whenever a COM component is instantiated, its class object or class factory is called to do the initialization. When this takes place, the component’s interfaces are exposed to the client application.


Note:  

If you are developing your COM objects in C++, your class factory might indeed ultimately call a C++; class constructor to actually create the COM object. In general, though, COM objects are created using a static class object (C++ terminology) whose singular purpose is to create instances of a given COM object. How this is accomplished is encapsulated within the class object itself. You probably don’t know how any given COM object is created, nor should you be concerned. COM will handle the details for you.


A Real-World View

Imagine that you are creating a checkbook application. Looking at this application simplistically, you might need a method that would deposit funds, one to withdraw funds, and probably another one to calculate the balance. To implement this as a COM object that could be reused in a banking application, you would need to define an interface for the checkbook that would expose your three methods. Figure 10.1 provides a representation of how this might be shown in a design methodology.


Figure 10.1  A design representation of the checkbook COM object.

If you are new to COM, you might be wondering how one object might know about another’s interfaces, or how to invoke the methods that the object contains. Let’s find out a little more about how COM really works.


Note:  

COM objects exporting interfaces for another’s use are called servers. Those objects that use another’s interfaces are referred to as clients. The interfaces themselves are typically named according to their function, although they are usually preceded with a capital I, as in ISomeCOMInterface.


The IUnknown Interface

COM’s architects knew there must be some mechanism for querying an interface to determine its capabilities and to provide for the interface’s reference count, which determines how long the interface remains available for use before destruction. They designed a very special interface, which they called IUnknown. This interface is so important that a basic tenet in the COM specification requires that all COM interfaces support the IUnknown interface in addition to their specialized interfaces.


Note:  

As I mentioned, the COM specification dictates that every COM component must support the IUnknown interface. Every interface defined for the component must be derived from IUnknown or from another interface that is itself derived from IUnknown. This is referred to as interface inheritance.


For the longest time, I was confusing interfaces and methods and their relationships. It wasn’t apparent to me that the interface was nothing more than a directing mechanism to get to the method that I needed. A COM interface provides access to a table that contains pointers to the object’s methods. This table is referred to as the vtable, and it serves the same function as the C++ vtable, which is a table of virtual pointers to constituent object methods. Figure 10.2 depicts this relationship. When a client application obtains an interface pointer, it can invoke any method that is exposed by the interface. The interface pointer is essentially a redirected memory pointer to the vtable. The pointers in the vtable are offset from this internal pointer as defined by the Interface Definition Language (IDL). Listing 10.1 is an IDL listing for this chapter’s example, a CheckBook application.


Figure 10.2  The representation between an interface and its methods.

Notice from Figure 10.2 that this interface contains not only the three methods (Deposit(), Withdrawal(), and Balance()), but also three additional methods. If you are following this discussion or have previously learned a little bit about COM, you probably already know the answer to the question “Where do the other three methods come from?”

Back to IUnknown. IUnknown has three methods. These are QueryInterface(), AddRef(), and Release(). Look familiar? COM specifies that the ICheckBook interface must be derived from IUnknown, and therefore the interface will contain not only the checking-related methods but also the IUnknown methods.

Listing 10.1 The IDL Listing for the ICheckBook Interface COM Object


// CheckBook.idl : IDL source for CheckBook.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (CheckBook.tlb) and marshalling code.

import “oaidl.idl”;
import “ocidl.idl”;
    [
        object,
        uuid(6EC5AB0E-A254-11D2-9D87-000000000000),

        helpstring(“ICCheckBook Interface”),
        pointer_default(unique)
    ]
    interface ICCheckBook : IUnknown
    {
       [helpstring(“method Deposit”)] HRESULT Deposit(long lAmount);
       [helpstring(“method Withdrawal”)] HRESULT Withdrawal(long lAmount);
       [helpstring(“method Balance”)] HRESULT Balance();
    };
[
    uuid(6EC5AB01-A254-11D2-9D87-000000000000),
    version(1.0),
    helpstring(“CheckBook 1.0 Type Library”)
]
library CHECKBOOKLib
{
    importlib(“stdole32.tlb”);
    importlib(“stdole2.tlb”);
    [
        uuid(6EC5AB0F-A254-11D2-9D87-000000000000),
        helpstring(“CCheckBook Class”)
    ]
    coclass CCheckBook
    {
        [default] interface ICCheckBook;
    };
};

The QueryInterface() method, as its name implies, will answer the question “Is this interface the same as the interface I am looking for?” The AddRef() and Release() methods are used for reference counting. More about that later.


Note:  

The vtable is a redirecting mechanism to allow the client to execute a method exposed by the interface. Whenever a COM client obtains an interface pointer, it has a pointer to the vtable. By using pointer dereferencing, the client can invoke any method exposed by the interface—a simple, yet extremely powerful technique!




The QueryInterface Method

Whenever an application creates an instance of a COM object, it receives a handle to an interface exposed by the object. To find other interfaces on the object, the client application must invoke IUnknown’s QueryInterface() method. At this point you might be asking how an interface is really defined.

How do you use QueryInterface() to find another interface? Suppose that the name for the Deposit interface is not unique. It is not beyond the realm of possibility that the interface name Deposit might be used by another COM object. You need to pass QueryInterface() a unique interface identifier, or IID.

Now back to the IDL listing. Notice the line above the interface definition. Enclosed in square brackets is the interface’s Universally Unique Identifier (UUID) definition. The RPC-defined UUID is uniquely defined in time and space. In COM parlance, it is typically referred to as a Globally Unique Identifier (GUID). This particular GUID denotes an interface, so it is yet again renamed to IID. How can you be certain that these interfaces are indeed unique, and that some other machine doesn’t create an identifier that is identical to yours? First, the identifier is unique in time, but that still doesn’t guarantee that they will be unique. Applying a unique machine identifier to the GUID will guarantee that you have created a unique identifier for the interface. Given these unique IIDs, QueryInterface() can locate the requested interface.


Note:  

A GUID that defines an interface is referred to as an Interface Identifier (IID). A GUID that defines a class is referred to as a Class Identifier (CLSID). The term GUID is essentially interchangeable with either.


If you are following me to this point, you might also be wondering how all this applies if the COM object is updated, or one of the interfaces has changed. Uh-oh! The COM specification dictates that interfaces cannot change. And that includes the methods that it supports. If you change one of the methods that it supports, you change the interface definition itself. So how do you deal with this dilemma? Whenever a COM object is updated, a new interface must be created.

At first, this might seem like overkill, but to maintain the portable nature of COM objects, their interfaces must remain unique. If you need to add functionality to a method, or add more methods to a COM object, it is necessary to follow the rules. This ensures that users of your previous COM object aren’t adversely affected whenever they attempt to use a new version of your COM object.


Note:  

The COM specification dictates that an interface definition cannot change. If you need to make changes to your existing interface, you have effectively defined a new interface.


If you go back to this chapter’s CheckBook application, you might want to add a CalculateInterest() method. Because the COM specification doesn’t allow you to do this, you must create another interface for your object. Let’s take a closer look at this.

In this case, you have an ICheckBook interface with three methods. Assuming that you want to add another method, you can either add a completely new interface with just the one (new) method, or you can add another interface that has your new method yet also inherits the existing methods from your old interface. Because the first method will add only your new method, the client using your COM object would have to know about the existing interface in addition to your new interface. This is not always practical or prudent, although there might be times when this is an effective alternative. On the other hand, by inheriting a new interface from your old interface, your new clients only have to know about (or query for) the new interface. This happens often in real-world situations. (ISomeCOMInterface becomes an enhanced ISomeCOMInterface2.)

Suppose that you want to modify or enhance your existing methods. This problem is a little more difficult in the COM world. Because you can’t change an interface, you can’t update your existing methods. You would have to provide a new interface that contains new versions of these methods. In this situation, you cannot inherit from your old interface, because you would probably name the methods identically to the pre-existing ones. Figure 10.3 is a representation of the new COM object with the new interface derived from the old interface. ICheckBook2 is derived from ICheckBook and contains the new method that you need.


Figure 10.3  The representation of the new checkbook COM object.

Reference Counting

Remember that a COM object is a specific instance of a class. It is not uncommon to need to use a COM object more than once. When a client starts an object, it might not be the only one that wants to use it. If other clients use the object, they do so by acquiring a pointer, either from the originating client or by invoking CoCreateInstance(). Whenever an object passes out one of its pointers, it increments the reference count for that interface. Whenever the client is done with the interface, the reference count is decremented. When the reference counts for all interfaces on an object are zero, the object can be unloaded. The AddRef() method is called by the CoCreateInstance method when an instance is created for a client. The client calls Release() to decrement the usage count. When a client passes an object’s pointer to another client, the new client must call AddRef() to tell the object that it is using the interface.


Note:  

It is very important that clients follow the rules of reference counting. It could be problematic for an instance of an object to remain in memory when not used anymore.


Servers, Clients, and Classes

COM is commonly viewed as a client/server model, and for good reason. As I mentioned previously, an application that uses the services of a COM component is commonly called a client. A COM component that exposes methods and provides services is referred to as a server. This approach simplifies the problem solving solution and lends itself to the definition of reusability. Whenever a COM component crashes, or the server that encapsulates the COM components develops a problem, the client can deal with the situation gracefully. If the client/server relationship were not implemented in this fashion, clients would have to know implementation details to handle these situations gracefully.

COM is unique in allowing clients to also be servers. By having components share pointers with each other, you can develop truly robust peer-to-peer applications. This flexibility leads to obvious advantages over other component models available today.

A COM object is implemented in a server. This server can be in the form of a Dynamic Linked Library (DLL) or can be implemented as a separate executable. A DLL is loaded at runtime, and can be unloaded when no longer in use. Servers take on three forms:

  In-process server—The object is implemented in a DLL and resides in the same memory space as the client.
  Local server—The object is implemented in a separate process on the same machine.
  Remote server—Objects are executed on a separate machine. I will discuss this topic in the section that covers DCOM.


Note:  

Each object is an instance of a class. The unique identifier for a class is commonly referred to as a CLSID. A client uses this CLSID to create an instance of the COM object. A server can have more than one object of a specific class, and can also support multiple classes.




To a client, the implementation remains the same regardless of which type of server the object resides in. Without this transparency a client would not only have to know about the interfaces on a component, but the implementation details as well. This defeats the purpose of the COM standard.

As you are undoubtedly realizing, the COM specification has dictated a standard by which clients written in any language can access components implemented in any language. This solves a multitude of problems when developing applications, as developers can now concentrate on the “building-block” approach to defining workable solutions to common everyday problems. Let’s expand what you know to further define COM.

The COM Runtime Environment

The COM runtime environment is a suite of system components that enables COM to work. Using the COM runtime, clients can make IUnknown calls across processes or over a network. It is the mechanism that provides the ability to establish connections between components. It also contains system calls necessary to instantiate components. An application creates a COM component by passing the CLSID of the component to the COM runtime. The CLSID is a key stored in the registration database (the Registry) that identifies the component, some other specific component features (in-process and so on), and where it resides on disk for later instantiation.

As I mentioned earlier, a COM component can either reside on an executable server or a DLL server. There is a difference in the way that COM starts the components. For an executable, COM will start the executable and then wait for it to register its class factory through CoRegisterClassFactory. For a DLL, COM loads the DLL into the client’s address space and calls DllGetClassFactory(), a mandatory DLL exported function.

When a client instantiates a component and requests an interface pointer, COM will create the component and then pass back the requested interface pointer to the client if the COM object supports that interface. Object creation is accomplished using the IClassFactory interface. The client worries only about communicating with the component through the returned interface pointer. It doesn’t care about the object’s specific implementation details, or even where the component resides (local, remote, and so on).

Figure 10.4 defines a simple startup relationship between a client, a COM object, and the COM runtime environment.


Figure 10.4  The COM runtime environment.

Defining the Class Factory

A well-designed application would use an instance of a component as many times as possible to gain the benefits not only of reusability, but also of performance. If a client were concerned only about creating one instance of a component, the CoCreateInstance() call would be used to create it. However, if the client wants to create multiple components, it could do it directly through the server’s class factory. Class factories expose IUnknown like other COM components, but like most interfaces of interest, they go a little further in what they are able to do.

Even when only one instance of a COM class is created, the COM runtime calls the object’s class factory to instantiate it. It makes sense to provide for object creation using one and only one mechanism.

The IClassFactory interface defines the methods needed to create the instances of its COM object for the client. The IClassFactory interface defines the two following methods:

  CreateInstance—The CreateInstance does exactly what its name implies; it creates an instance of the specific object that the factory is designed to create. Because the class factory is concerned only with a specific COM object, the client does not need to pass in the CLSID. However, the client does need to specify which interface it requires. It does this by passing in the IID.
  LockServer—Forces the newly created COM object to remain in memory. This is in addition to the standard COM reference counting capability. By locking the factory’s server in memory, COM guarantees that individual object’s reference counts will not inadvertently remove the (static) class factory from memory until all the objects that it creates have been removed.

Some class factories also support the IClassFactory2 interface, which inherits from IClassFactory. This interface enhances the basic IClassFactory interface by dealing with licensing issues. I will cover this a little later in the chapter.

To directly access a class factory, a client would invoke the CoGetClassObject() method instead of the normal CoCreateInstance() COM API call. CoGetClassObject() requires a CLSID and an IID, as well as other parameters, just as CoCreateInstance() does. However, when you use CoGetClassObject(), you are given a pointer to an object’s class factory instead of a pointer to an instance of the object. Given this pointer, you can create as many specific instances of the object as you want (using IClassFactory::CreateInstance()). If you will require many instances of a given COM object, this is by far the most efficient approach.

How Are COM Objects Reused?

Suppose that an object were to change. I’ve talked about this scenario, and have determined that there are ways to make it happen. I discussed creating a new COM object that supported old interfaces. Here are some other ways:

  Containment—The term delegation is sometimes used to refer to containment. In COM, containment signifies that the outer object acts as a client to the inner (older) object. With containment, the outer object does not expose the inner object’s interfaces.
  Aggregation—Aggregation exposes an inner object’s interfaces as its own interface.

Containment is by far the most common form of COM object reuse. It is also easy to implement, although you must be aware of situations where the delegation goes several layers deep. If layer upon layer of COM objects contains other objects, error-reporting can sometimes be watered down or even masked when lower-level COM objects have a problem.

Aggregation is not without its problems either. Reference counting on an inner object can be cumbersome. Implementing aggregation requires the inner object to do work to support reference counting on the outer object. To solve this problem, the inner object must redirect all calls made on its IUnknown method to the outer object’s IUnknown method. To do this, however, requires the inner object to know about the outer object. This is done through the creation of the inner object. When the client invokes CoCreateInstance (or the object’s class factory), it passes its IUnknown to the inner object. This is sometimes referred to as the controlling unknown. If the controlling unknown parameter is NULL in these calls, the inner object knows that it is not being aggregated. Note also there is no requirement that any given COM object must support aggregation.

Marshaling and Threading

I mentioned earlier that a COM object could possibly be active inside the client’s address space, or it could be active somewhere else (to include over a network on another computer). Whenever you invoke a COM object’s methods, the parameters you pass into the method and the return information you receive from the COM object could require some manipulation. For example, the data might need to be converted to network order for transmission over the network to another computer. Or imagine you are passing in an interface pointer—your pointer is completely invalid on a remote machine!



COM handles these data transmission situations by a process known as marshaling. Marshaling is no more than a data conversion to safely, and sanely, transmit data from one process or address space to another. In addition, marshaling is used to transfer data from one thread of execution to another, so there is no requirement that any given COM object reside in the same thread as the client (although this is often the case, and as an optimization, you can force this to be the case). Let’s look at this in more detail.

Marshaling

You’ve just taken a “nickel” tour of COM. You know that the methods that you need to invoke can reside in the same process, a different process, or even on a different machine. One of the nice things you can derive from this is that COM provides a transparent mechanism for the client to invoke the desired COM methods. I really haven’t discussed what unknown entity makes all this possible.

You know that if the COM object is implemented as an in-process server, you are simply provided pointers in memory to the object’s methods. What happens if the COM object actually resides in another process? You can’t simply be handed a pointer to memory to another application. To give you the illusion that you are communicating with an object in process, COM provides a mechanism known as a proxy. The proxy is a COM object that presents the same interface for the real COM object.

When a client invokes a method on an interface, the proxy will pick it up, package it, and send it to the real COM object through some sort of interprocess communication mechanism. There is another step in this process. When the proxy sends the package, it sends it to a stub object, which will unpack the information for the real COM object. This packing of information is referred to as marshaling, while the unpacking is referred to as ;. If the real COM object resides on the same machine, an optimized form of the Remote Procedure Call (RPC) mechanism called Local Procedure Call (LPC) is used. On the other hand, if the real COM object resides on another machine, the interprocess communication mechanism is actually performed as a true RPC call. Figure 10.5 depicts this relationship.


Figure 10.5  The proxy/stub relationship.

Marshaling is structured for each interface. Essentially, the marshaling code for the interface knows how to pack and unpack the information for the methods on the interface. The client marshaling code must know how to pack information and unpack results, and the server marshaling code must know how to unpack information and pack results. At this point, you might be asking how to create the marshaling code. The best way to accomplish this is to use the IDL language to create what you need automatically. Creating the marshaling code through the IDL also makes it easy to create the proxy and stub code. The Microsoft Interface Definition Language (MIDL) compiler will generate the necessary code for you.

In many cases, the standard marshaler handles the data packaging for you. If you pass integers, doubles, certain types of strings, and even interface pointers as parameters to a COM method, the standard marshaler takes care of things for you.

If you are really feeling brave, you might want to sit down and write your own marshaling code. I have yet to do that, as the MIDL has met all my needs to this date. That option, however, is available. The process of developing your own marshaling is simply referred to as custom marshaling (as if it would be named anything else). The question that is probably floating around in your mind now might be, “Why should I do custom marshaling?” One reason would be to improve performance. If you know the object that you are writing the marshaling code for, you can create better-performing code. Limiting the information packaged and unpacked can also improve performance.

Automation Interfaces and Early Versus Late Binding

COM also supports automation, such that scripting languages can access a COM object’s capabilities. If you use a given COM object using a special automation interface, IDispatch, you can use the standard marshaler and some special invocation mechanisms to access the COM object’s functionality with no foreknowledge of the other interfaces the COM object supports. This is critical for scripting languages because they are typically interpreted rather than compiled. The scripting engine has no idea what COM object support will be required until the COM object is actually needed. (Chapter 17, “Scripting Your MFC Application,” has more information.)

This is accomplished through the use of a type library, which is a tokenized form of the original IDL used to create the COM object’s interface(s). The MIDL compiler not only can build proxy/stub code, as you saw previously, but it can also build the type library. The type library includes all the information that a client needs in order to determine the object’s interface information.

If you access the COM object’s methods directly, you are using early binding. That is, you determine what functionality you will use when you compile your client code. However, as I mentioned, scripts do not work in this fashion. They access the object’s type library to see if the required functionality they need (coded into the script) is in fact supported by the COM object at runtime. This is known as late binding. If the script author claimed some COM object would support given functionality at runtime, but the script found no such capability when actually executing, the scripting engine will terminate the script with an error.

Most automation clients use IDispatch, coupled with late binding, to invoke the COM object’s methods. However, if you want to access the type library directly, there are two interfaces you can use to do so. These are the ITypeLib interface, which enables a client access to the library as a whole, and the ITypeInfo interface, which provides the client the necessary informational structure to work with the object in question. On the surface, this might appear trivial, but it is anything but! A client must first ask the ITypeLib interface some information to get information on the ITypeInfo interface, and then in turn use the ITypeInfo interface to get information regarding the objects that the type library contains.

Here is a list of some methods that are exposed by the ITypeLib interface:

  FindName—This method will return a pointer to any number of ITypeInfo interfaces found that contains the name.
  IsName—Determines whether an object exists in the type library.
  GetTypeInfoOfGuid—Finds the ITypeInfo interface based on a GUID.
  GetTypeInfoCount—Returns the number of accessible objects in the type library.



When the client has the information about the ITypeInfo interfaces in the library, it can then use that interface to find information about the object in question. Here is a small list of ITypeInfo methods.

  GetTypeAttr—Returns information about the TypeInfo object.
  GetFuncDesc—This is the workhorse for dynamic binding. This method will return the information about a method on the interface desired.
  GetVarDesc—Returns parameter information.

The COM API call LoadRegTypeLib()can be used to acquire the pointer to the type library for an object. Simply pass in the CLSID for the object in question.

Threading

In today’s computing industry, most processes are multithreaded, which enables a single application to perform a multitude of tasks almost simultaneously. There are enormous advantages to making a process multithreaded, but there are also drawbacks. Record-locking scenarios in database applications would be one.

COM provides support for multithreading in different ways. Early on, when threading became an issue, the apartment model, or single-threaded apartment (STA) was coined. The STA model implies that Windows itself manages the object’s data access using a standard message pump. Two clients cannot access a single COM object simultaneously because the COM object uses a message pump. COM itself forces calls to the COM object to be transformed into Windows messages, sent to the object’s message queue, and acted on in order (first in, first out).

Free threading appeared with Windows NT version 4. Free threading is essentially the multithreading of the COM object, which will be active in the multithreaded apartment (MTA). The essential difference between COM objects in the STA versus objects in the MTA is objects in the MTA must now coordinate their data access using traditional multithreaded techniques, such as semaphores and mutexes. Windows relinquishes the data access responsibility to you, so you gain performance benefits at the cost of additional data access code.

COM, OLE, and Automation

Now you are getting to the point where you need to see benefit from all this really cool technology. I briefly discussed OLE, and then jumped right into the COM architecture. Now it’s time to see COM work.

As I write this, I am using a word processing application that supports compound documents. If I needed to, I could insert a spreadsheet, or pull up the help window to show me how to format my margins. Let’s say that I wanted to use the spreadsheet to calculate the number of times that I’ve used the word “mechanism” in this chapter. Suppose that I wanted to provide the output in bar chart form and somehow place that back into the help file information regarding proper grammatical structure. Okay, I think I’m going a little overboard, but you can see where I am going! In the old days, if I wanted to use a spreadsheet to calculate information that I wanted to incorporate into my word processing program, I would need to move back and forth between the two applications. It would mean copying (cutting and pasting) the information that I needed. Another method would be to write macros if the spreadsheet and word processor supported them. Now, however, I can run the functions of the spreadsheet right from my word processor, and automatically format its output.

This amazing capability is brought to you by COM (and OLE) and the capability for applications to expose their programmability through the COM paradigm. The capability to program this functionality is referred to as automation.

Suppose that a shipping clerk needed to verify an end-of-day report that would be run against an inventory spreadsheet and then create a new inventory spreadsheet. By developing an application, or a scripting file that used information in a shipping spreadsheet to remove items from an inventory spreadsheet, the developer would simplify the shipping clerk’s life. This is the beauty of automation.

The goal of automation is to let an application expose the services that it contains. COM is the logical choice for enabling applications to relay their information. If every application or scripting program could support pointers and pointer traversal, the problem would be solved. However, some languages still have a tough time traversing the vtable. Some languages don’t directly support pointers, such as Visual Basic. To get around this problem, a universal interface was developed that would allow languages such as Visual Basic access to a COM component’s methods.

IDispatch

Any application that exposes its functionality can do so through the IDispatch interface, as I mentioned previously. Listing 10.2 is the actual definition (OAIDL.H) of the IDispatch interface. Notice that it is derived from IUnknown. Also notice the type information methods that allow marshaling (late binding) to take place.

Listing 10.2 The Definition (oaidl.h) for IDispatch


interface IDispatch : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
            /* [out] */ UINT __RPC_FAR *pctinfo) = 0;

        virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo) = 0;

        virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId) = 0;


        virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,

            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr) = 0;

    };

So what makes it so different from other COM interfaces? How can a program that doesn’t support pointers interface with IDispatch? Unlike other COM interfaces, it provides a method called Invoke that can be used to actually invoke the methods that the COM object supports. A client can invoke any method in the COM object by calling the IDispatch Invoke method. But how does this work?

This is done through a dispinterface, which is also known as dispatch interface. The dispinterface specifies the methods that are available through the IDispatch Invoke method. If you think this sounds a little like a vtable, you’re right; a dispinterface is similar to the vtable in that it contains a list of methods. But, unlike the vtable, the methods defined in a dispinterface are uniquely identified with a DISPID (an integer). This DISPID uniquely identifies the method to be invoked.

Whenever a program such as Visual Basic creates an instance of a COM object, the IDispatch interface is returned as a handle. Visual Basic, for example, has a function called CreateObject that accepts the CLSID of the object/application to instantiate. Because these programs don’t really support pointers, they use the value (handle) of the created instance to call the Invoke method, passing in the desired dispinterface for processing. Listing 10.3 shows a simple Visual Basic example that doesn’t do anything but show the calling structure.

Listing 10.3 A Visual Basic Example of Creating a COM Object


Sub DoSomething()
    Dim OurObject As Object
    Set OurObject = CreateObject(“Excel.Application”)
    Order = OurObject.Sort()
End Sub



The IDispatch::Invoke method is nothing more than a conceptual switch statement that will find the method identified by the DISPID and invoke it. This “behind the scenes” mechanization gives the appearance of a vtable, which allows programming languages that don’t support true pointers to still support COM.

Automation Servers, Objects, and Controllers

Any application that exposes its services using automation does so by providing these services as COM objects. Applications that do this are referred to as automation servers. However, the term ActiveX comes into play here. Automation servers are now referred to as ActiveX components acting as a server.

The objects that an automation server creates, on the other hand, are referred to as automation objects. In keeping with the “name game,” they are now simply referred to as objects or ActiveX components.

Applications that used applications through their IDispatch interfaces where commonly referred to as automation controllers. Now, however, they are referred to as COM clients. This naming convention actually makes sense. When you pull off all the names, you are strictly talking about COM.

Supporting Both IDispatch and IUnknown

Dispinterfaces make it easy for some programming languages to implement COM objects. Using a dispinterface, however, is slower than using the vtable method. There is a way to provide support for both interfaces. This is referred to as a dual interface. Dual interfaces inherit from IDispatch. If you look closely, IDispatch’s vtable contains the IUnknown interfaces as well as its own methods. By having both the QueryInterface method and the Invoke method, a COM object supports dual access—directly using the interface pointer or indirectly using the dispinterface. This allows a programming language such as C++ to use the IUnknown methods (for better performance), yet also allows script-based languages to use the dispinterface methods. The COM object now can be used by a wider variety of clients.

Persisting COM Data

As an application developer you understand the importance of persisting data. An application that can close and then be reopened to continue where it left off is a definite production boost. COM objects, if you think about it, need to do this. Take, for example, the checkbook COM object. When the Balance method runs, it calculates the running balance in the checking account. When the server for the checkbook closes, that balance needs to be persisted, or the entire application is worthless.

There are many ways to store data permanently—file systems, databases, or the Registry, just to name a few. These are referred to as persistence servers.

In most cases, the client application must be able to tell the COM object to persist its data. A COM object’s capability to persist its data is normally provided by defining two sets of interfaces. The first is defined as a storage mechanism known as structured storage. The second is the IPersist interfaces.

Structured Storage

Data comes in many forms, and it can also be stored in many forms. Flat files are commonly used for simple objects, but these don’t always solve the problem. In many cases, several COM objects can use the same file, storing data in different parts of the file. But what about storing data on a different machine? Structured storage comes into play when several objects need to access the same file. If the objects storing data to the file were created by the same developer, the structured storage mechanism would really be needed. In the real world, however, COM objects are developed by many different sources, which made it necessary to develop a mechanism to provide consistent storage for a wide range of object data requirements.

Structured storage allows many objects to access and maintain a single file. But how does it do this? How to provide some mechanism inside a file that allows different objects to consistently store information is not readily apparent. You know that COM objects provide consistent interfaces for allowing clients to communicate not only with them, but also with other COM objects. Why not implement a system inside a file similar to that of COM? To do this, a single file is made up of any number of storages, which function in a similar manner as directories do for a file system. Files that contain multiple storages are referred to as compound files.

Each file supporting structured storage contains a root storage, as depicted in Figure 10.6. Each storage for the file is connected back through the root storage. Notice the other element inside the structured storage. This is the stream, which supports the actual data in the file. The storage represents a directory, and the stream represents a file. The storages inside the structured storage file basically keep track of the streams below them. The streams are simple mechanisms that store streams of bytes. These streams are very basic, with no indexing or complex storage overhead associated with the data.

Each component that accesses a compound file can be attached to its own stream in which to store its own data. It can also create its own hierarchy, adding additional storages and streams for compartmentalizing its data. Each component, having its own storage area, doesn’t have to rely on maintaining a separate file, or where other components might be storing their data. This is an obvious performance advantage!


Figure 10.6  The structured storage paradigm.

Although there are obvious advantages to persisting data by this method, there are also disadvantages. The structured storage file must contain the overhead to manage the storage hierarchy. These files can become quite large, which adds unnecessary overhead for the operating system, not to mention the disk space requirements. Let’s return to my earlier statement, “Why not implement a system inside a file similar to that of COM?” By viewing storages and streams as COM objects, you open up the architecture of the structured file. Let’s take a closer look.

Storages have an interface called IStorage, and streams have an interface called IStream. COM objects interact with their storages and streams through these interfaces, thereby keeping the COM paradigm intact for structured storage. Each of these interfaces contains many methods similar to working with a file system.


Note:  

Structured storage also supports transactions. As it relates to the structured storage file, a transaction is a group of data reads and/or writes where all must succeed in order to save any of the data.




Although structured storage gives the COM object the capability to persist important data, it is actually the client that tells the COM object when to store the data. COM doesn’t dictate what interfaces must be used—clients and objects are free to define their own. As with any good specification, however, there are some defaults that clients and objects are free to use.

  IPersistStream—This is probably the most used interface. It allows a client to tell the COM object when to store or retrieve stream data.
  IPersistStreamInit—This interface, which is inherited from IPersistStream, adds another method to signify to the COM object that its persistence is being initialized.
  IPersistStorage—This interface tells the COM object that the client is requesting it to save its data using a storage. (Remember, the storage controls all streams and storages below it.) A COM object that would implement this interface probably controls many data items that would be maintained in separate streams.
  IPersistFile—This interface tells the COM object to use a flat file for its storage mechanism.
  IPersistMemory—This interface is similar in many respects to IPersistStreamInit. However, it references specific memory regardless of initialization.
  IPersistPropertyBag—This interface tells the COM object that the client wants to use its data as property sets.
  IPersistMoniker—This interface tells the COM object that the client wants to load and save data remotely through a moniker. (I’ll talk about that in the next subsection.)

Identifying COM Data (Monikers)

I’ve talked about specific naming conventions for interfaces and uniquely identifying the COM object in time and space. But what about a specific instance of an object? In fact, the COM specification doesn’t indicate any way for you to identify the instance. How then is this important if COM doesn’t specify how you are to do this, and why would you want to do it? Suppose that your checkbook COM object is being controlled by a savings and loan application that is Web-aware. It controls many accounts, each managed by a checkbook COM object. The savings application must keep track of each specific instance, but how do you do this?

To adequately name a particular instance of a COM object, you need to know about its interfaces, methods, and most importantly its properties and data. Your savings and loan application must identify not only a specific COM object, but also the balance data that the object maintains. There are many ways to make this identification. One method is to create the object through CoCreateInstance and pass it the object’s CLSID. The client can then use one of the persistence interfaces that I discussed previously to load the data that it needs. Of course, the client would need to know the object’s CLSID to do this, which is not always practical or available.

A moniker is a name for a specific instance. You are probably asking how the client would come to know about a moniker when no easy method of knowing the COM object itself is available. In fact, the moniker is a combination of the CLSID and the specific data for the object. But a moniker is more than a name—it is a COM object! If you are not confused now, you probably will be, but let’s dig a little deeper. As stated, the moniker is a COM object, and exposes this capability through an interface known as IMoniker. Each moniker has everything that it needs to create an instance of the object that it represents. It does this through the BindToObject method of the IMoniker interface. The client will invoke this method, passing it an IID of the interface it requires on the target object. The moniker can then instantiate the object and pass back the desired interface to the client. When the client has this information, it can then start using the object. This appears similar to what the COM library does, but the moniker maintains the necessary initialization data for a specific instance of the object.

Transferring Data

Transferring data between applications, systems, and even objects is the most common function that software undertakes. It makes sense that COM provides a unique mechanism to perform data transfer. Uniform Data Transfer (UDT) contains an interface, IDataObject, which provides an identifiable way of transferring data between clients and objects.

Requiring a client to continually request data from an object implies bad performance. If the object were smarter, it would automatically update the data that the client is expecting from it. Uniform Data Transfer does just this, but is fairly limited. Another method, referred to as connectable objects, is commonly used for this purpose.

Uniform Data Transfer

A plethora of options are available when it comes to transferring data between software applications. Windows provides the clipboard, Dynamic Data Exchange (DDE), and recently—with the advent of Microsoft Message Queue (MSMQ)—an application can even take advantage of a full message processing system to transfer data.

An object that supports the IDataObject interface is commonly referred to as a data object. Data objects can make many forms of data available to their client through the IDataObject interface. A client can access data in many different objects, in files, and in memory through one object’s IDataObject interface. But how does a client know how to handle data transfer with objects it knows nothing about? What about learning how the data might be represented?

Because UDT is a standard, it must define certain characteristics and mechanisms that all participants must know about and implement. Data objects use the FORMATETC data structure, which is a subset of the Clipboard. It contains information regarding the data formats, device information, data instance information, and role information. Data objects also support and use the STGMEDIUM structure, which describes the medium to store the data.

Drag and Drop

One of the most common productivity tools in use today is the Clipboard model, which provides the ability to drag and drop and cut and paste data between applications. The application that contains the data from which the copy is made is referred to as the drop source, and the target is referred to as a drop target. The application or object that is the drop source must support the IDataObject interface (that is, it must be a data object). The target must support and implement the IDropTarget application. But how is the data actually transferred?

Notice in Figure 10.7 that the target actually receives a pointer to the drop source’s IDataObject interface, and in turn invokes the methods on that interface.


Figure 10.7  The IDropTarget actually gets a pointer to the IDataObject interface.

Event Notification

To take advantage of UDT, you need a way to enable an object to update a client whenever an important piece of data changes. This greatly improves performance. The object needs a way to inform the client that data has changed, and then must be able to get the data to the client. The IAdviseSink interface provides just this capability. The object must support the IDataObject interface and provide an interface that will define what data requires notification. There is no standard interface for this, so the object must define one that the client knows about. The client must implement an interface by which the source notifies it of data change.



Connectable Objects

Another method by which an object can communicate with its client is that of connectable objects. Connectable objects provide a method by which logical connections between an object and its client can be made. To do this, the object must support an interface commonly referred to as an outgoing interface. An incoming interface is one that receives, or sinks, the connection information. An outgoing interface is one that is commonly referred to as a source interface. Figure 10.8 depicts this relationship.


Figure 10.8  Connectable object topology: incoming and outgoing interfaces.

In Figure 10.8, the connectable object contains an incoming interface to receive a request from the client. The Sink object in the client contains an incoming sink interface for communicating with the connectable object. There is no rocket science going on here. These interfaces define a standard mechanism for defining connections, and are not too different from any other interfaces that can be defined.

An object must support IConnectionPointContainer to fully support connection points. This interface basically informs the object and its client which interfaces are outgoing interfaces for the data object. In Figure 10.8, the client will implement its sink interface, query for the IConnectionPointContainer interface, and then ask for a specific connectable point object for the sink interface. The client will then pass its sink interface to the connection object to make the connect for communicating between the objects.

Whenever the connectable object contains an outgoing interface, the object has to implement a connectable point. This is also a small object that is responsible for maintaining the list of sink interfaces with which it needs to communicate. To do this, the client that implements the sink must register itself with the connection point object. The IConnectionPoint interface is the interface that the connection point must expose. The Advise method and Unadvise methods are used by the client to register its sink interface.

DCOM

COM is a very extensive technology, and I’ve only covered the basics at a very high level. I’ve done so primarily at the local server level, with all clients and objects running on the same system. But what about applying this technology to a distributed network with objects running on different machines? Distributed COM (DCOM) is the implementation of COM at the networking level. And amazingly, DCOM introduces very little deviation for the client for implementing COM. Objects on a local server or a distributed network are instantiated and communicated with in a similar manner, giving the appearance that the client is working with standard COM objects. There are, however, things going on under the covers that give this appearance.

Beyond the capability to start and manage remote objects, DCOM has to take into consideration all the communication and security aspects that come into play when working with networks. DCOM provides security services in such a way that clients don’t need to implement specific security code. Applications or objects that are familiar with the security services Windows networking provides can have the capability to implement specific security services via the DCOM infrastructure.

DCOM provides three extensions to standard COM for working in the distributed environments:

  Object creation—DCOM provides some details that enable object creation for distributed computing.
  Invoking methods—DCOM provides a protocol for invoking methods on a remote object.
  Security—DCOM provides several mechanisms to handle network security issues, probably the biggest addition to COM.

Object Creation

COM provides a simple and effective standard for creating objects. Objects are created through the COM runtime environment or by using monikers. DCOM allows object creation in a similar manner, but several things must be taken into consideration.

CoCreateInstance

Normally, one calls CoCreateInstance to create the object, and then QueryInterface to get interface pointers for the object’s interfaces. The client can use the same method to create a remote object. Passing both a CLSID and an IID, the client can call CoCreateInstance to create the remote object. With this method, the client doesn’t have to know or be concerned with where the object resides. But how does the system know where the object is? You might be thinking that the COM runtime might store this in some form. The logical place, however, is the Registry.

You know that the only real difference in the object creation is where the object is stored. This machine information is stored in the Registry! The COM runtime will go to the Registry to look up the object and instead finds a machine name. It will then go to the Registry on that machine and locate the object. Figure 10.9 shows this process.


Figure 10.9  CoCreateInstance for remote objects.

You can tell from Figure 10.9 that this is a lot of work, but you also see that to the client, it appears the same as if the object were created locally. The COM runtime will look into the Registry in order to map the CLSID to the file of the object to activate. If COM finds that the COM object in question is a remote object, COM will then search the Registry of the remote machine (as indicated in the local machine’s Registry). COM will then create the object by running the executable (out-of-process server) or loading the DLL into a surrogate executable (in-process server) on the remote machine. When the server for the object is active on the remote end, COM will return the (marshaled) interface pointer back to the local client.

CoCreateInstanceEx

As you can see from what’s just been discussed, using CoCreateInstance to create your remote objects is cumbersome if more than one interface is desired. It is also fairly slow, because it is dependent on network and machine speeds as well as communications traffic. If only one interface is desired, CoCreateInstance is still sufficient. If, however, the client needs more than one or isn’t sure, a better way is to use CoCreateInstanceEx(). CoCreateInstanceEx() provides a way for the client to request a list of IIDs. CoCreateInstanceEx() will query the object for all the interfaces in the list and then return the entire list of pointers when it has them. This prevents the client from having to make multiple QueryInterface() calls on the remote object.

CoCreateInstanceEx() goes a step further and allows the client to specify where the remote object should be created. This allows the client to be dynamic and not rely on the local Registry. This also adds to the portability of a client implementing objects in a distributed system.



Object Initialization

DCOM allows for the creation of a remote object; the next step is to initialize it. This can involve loading persistent data, defining runtime variables, and so on in the typical COM fashion. This is usually done with either the IPersistFile::Load or the IPersistStorage::Load methods. If you think about it, however, doing this over a remote network might be inordinately slow. DCOM provides two ways to create and initialize an object in one step.

The first method is through CoGetInstanceFromFile(), which will create the object and initialize it from a file on the remote system. This is similar to the IPersistFile::Load method, but requires less overhead. The second method is CoGetInstanceFromIStorage(), except it loads and initializes the object from a structured storage file on the remote system.

Creation Through Monikers

Monikers can also be used to create remote objects, which is, in fact, sometimes the preferred method. Whenever a client calls the BindToObject method on the IMoniker interface, the moniker will call CoCreateInstance() with a CLSID from persistent data. Using the persistent data, the moniker will then initialize the object. It does this through information in the Registry. If a remote machine is allocated in the Registry, and it specifies a DCOM object, the object is created on the remote machine in much the same manner as mentioned previously.

Invoking Methods

After you’ve created your object, you have to be able to use it. You know that to a client the creation is basically hidden. What about being able to invoke methods on the interfaces given to you? For the client, there is no difference in the way that you invoke methods of a local object to that of a remote object. When the object has been created, and the client has an interface pointer, it can then invoke the methods on that interface just as it would for locally created objects.

You learned that for in-process servers, you are essentially calling vtable methods within memory of your process. When you invoke methods for an object instantiated in a local seer, you use a proxy and a stub. I also briefly stated that the same is true for the remote object. You need a proxy and a stub, but you also need the communication layer that transfers your data request to the remote machine, and receives back your responses from the remote machine. This layer, or protocol, is referred to as a Remote Procedure Call (RPC). I don’t have enough space in this book to discuss all the existing RPC protocols available, but I will briefly look at what is available in DCOM.

MS RPC, which Microsoft based on the Open Software Foundation’s Distributed Computing Environment (DCE), is what DCOM implements to carry out the communications between client and remote object.


Note:  

DCOM’s usage of MS DCE is sometimes referred to as Object RPC, or ORPC.


MS DCE and ORPC actually use two protocols. One is a connection protocol and the other is a connectionless protocol. The connection protocol, sometimes referred to as CN or CO, assumes that the underlying transfer protocol will transfer data reliably, whereas the connectionless protocol, referred to as DG or CL, assumes the exact opposite. To a client, however, these protocols appear similar.

A client must use some sort of binding information for the remote machine prior to making any ORPC calls. Binding information basically consists of the remote machine identifiers (such as an IP address) and what protocol combination to use. Other important binding information includes the port that identifies the process on the remote machine that will handle the request.

When a machine name is passed to CoCreateInstanceEx(), that name can be used to load some of the binding information. It is possible, and sometimes mandatory, for an object to pass binding information to another object. For the client to access or invoke methods on a remote object, it must first acquire the OXID for the server. When it has this ID, the object can rely on an OXID resolver. Every system that supports DCOM includes some sort of OXID resolver, which contains an interface called IObjectExporter. At first glance, it appears that the resolver is another COM object. It is not a COM interface, but an RPC interface.


Note:  

A server application that implements one or more COM objects for a remote client is referred to as an object exporter. An OXID identifies the object exporter.


The IObjectExporter interface contains three methods. These are ResolveOxid, SimplePing, and ComplexPing. Each OXID resolver has a table of OXIDs and their corresponding string bindings. It can also include string bindings for objects running on other machines. One thing that you might notice here is an apparent likeness of this RPC mechanism to a vtable or an IDispatch table. But when working with networks, you must also consider that some objects might be running on different systems with different operating systems. Although most systems utilize an ASCII string protocol, others use EBCDIC. The same is true as to how integers and consequently interface pointer definitions are stored.

RPC handles this mismatch of datatypes with a network format called Network Data Representation (NDR). NDR provides a common way of moving parameters across a network to machines of different environments. One thing that is not handled very well with NDR is that of interface pointers. Ouch!

DCOM to the rescue! Whenever an interface pointer needs to be transferred to another machine, DCOM uses object references (OBJREFS), which include the following elements:

  An OXID—An unsigned hyper (64-bit integer) value representing an RPC connection identifier for the server.
  Object Identifier (OID)—A unique identifier for the object itself
  Interface Pointer Identifier (IPID)—A unique identifier for the interface
  Binding String—String binding for the OXID Resolver on the remote machine

If the resolved binding information is not present in the local resolver table, the object must get the information from the resolver table on the remote machine and then store it locally. When this is done, the client or local object can then communicate with the remote object. All this communications layer is hidden through DCOM.

Security Issues

With any open network, there are security risks. DCOM also provides mechanisms that provide security layers to be applied to the communications. One such method, commonly referred to as activation security, is concerned with controlling who is allowed to create remote objects. Activation security uses the Registry for defining who is allowed to launch servers on its machine. Activation security also uses the Access Control List (ACL) to define who has privileges on a class basis.

Another mechanism is that of call security. Call security involves the following:

  Authentication—Authentication enables the object to determine the client’s identity.
  Authorization—Authorization provides a set of permissions for the client.
  Data integrity—Basically, data integrity uses a CRC or some other method to verify that the data was transmitted intact.
  Data privacy—Data privacy usually requires some sort of encryption to verify that the data is protected during the transmission.



Although I have only briefly discussed DCOM, you can see the benefit to the client application invoking remote objects. DCOM provides a broadening of possibilities without sacrificing the interface specification of COM.


Tip:  

I strongly recommend reading Inside Distributed COM by Guy Eddon and Henry Eddon (Microsoft Press, ISBN-1-57231-849-X). This book provides insight into all of the communications layers of DCOM. I would be hard pressed to give this technology adequate coverage in this short chapter.


Some Important Information

When you start working with COM, you will notice that there are several related items—items that are covered in the following sections.

BSTR

A BSTR is a type of string that is used by COM. Because there are no defined lengths or sizes for a string, as there are for other datatypes (int, long, and so on), it is wise to use the BSTR. If the COM object is to be used and deployed by Visual Basic or Java programs, BSTRs must be used extensively. The BSTR contains a length prefix that indicates the number of bytes for the string.


Tip:  

I would recommend using the CComBSTR ATL class for managing BSTRs. This class handles dynamic pointer allocation and deallocation, as well as pointer reference counting. See Chapter 16, “Using MFC and ATL,” for more information.


SAFEARRAY

If you’ve ever had to handle variable size arrays, you will understand the difficulty of trying to do so in Visual Basic. The SAFEARRAY is a mechanism that COM provides gives Visual Basic that capability. The array itself is a fairly complex structure. Take a look at Listing 10.4.

Listing 10.4 The Definition for a SAFEARRAY(oaidl.h)


1:    typedef struct  tagSAFEARRAYBOUND
2:        {
3:          ULONG cElements;
4:              LONG lLbound;
5:        }    SAFEARRAYBOUND;
6:
7:    typedef struct tagSAFEARRAYBOUND __RPC_FAR *LPSAFEARRAYBOUND;
8:
9:    typedef struct  tagSAFEARRAY
10:    {
11:        USHORT cDims;
12:        USHORT fFeatures;
13:        ULONG cbElements;
14:        ULONG cLocks;
15:        PVOID pvData;
16:        SAFEARRAYBOUND rgsabound[ 1 ];
17:    }    SAFEARRAY;
18:
19:   typedef /* [wire_marshal] */ SAFEARRAY __RPC_FAR *LPSAFEARRAY;

Lines 1 through 5 defines the SAFEARRAYBOUND structure, which defines the number of elements that will be defined in the array structure. At first glance, this appears to be somewhat confusing. Line 15 contains a definition of the actual data. Everything else is essentially metadata. The SAFEARRAYBOUND array is a bounding array (hence the name) that encapsulates the actual data in the array. The cDims field (line 11) indicates the number of dimensions applied to the array, and not the number of elements. Hopefully, you are beginning to see how this array makes it easy to define multidimension arrays in COM by using a standard passing mechanism. The cbElements field defines the size of each element in the array, and not the actual number of elements.


Note:  

The cDims field is also used for memory allocation of the array. Given the size of a single dimension of the array (SAFEARRAYBOUND), the number of dimensions is applied to determine the memory allocation size.


If you are developing ActiveX components or COM objects that will be used by Visual Basic applications, get to know the SAFEARRAY mechanism. Even though it is a little difficult to pick up and understand, there are plenty of examples out there to get you started.

HRESULT

There is no magic to the HRESULT. It is nothing more than a 32-bit integer that is used exclusively in COM to help define result types. These integers contain important information about errors that can occur in the COM environment. HRESULTs contain three sections:

  Severity—Basically a bit indicating success or failure
  Facility code—Details where a particular error occurred
  Information code—Information as to the specific error within the specified facility

COM provides two macros for working with HRESULTS. The SUCCEEDED(hresult) and the FAILED(hresult) are Boolean values that allow a COM developer to check error codes prior to proceeding.


Tip:  

Because of the very nature of COM, always use error checking with the SUCCEEDED and FAILED macros. Not verifying error conditions prior to continuing to use the object could yield indeterminate results.


VARIANT

The VARIANT is basically a very large union of datatypes. But more than that, the VARIANT denotes the datatypes the COM standard marshaler is able to automatically marshal for you. The VARIANT is covered in some detail in both Chapters 16 and 17.

Further Reading

Many books on the shelves provide a wealth of information in a very informative manner, and many others are nothing more than a waste of money and shelf space. To keep the peace, I won’t list the books that I think are a waste of time, and I won’t list all the books that I think have merit.

You might be wondering why I would promote other books in this fashion. I am a firm believer in learning, and I am always on the lookout for good teachers. Kenn Scribner, who contributed to this book, is one of those few who can articulate a complex subject in a simple and elegant manner. If you have never been exposed to COM, I would hope that I have imparted some small nugget of information, and presented it in such a manner that only serves to pique your interest. Remember, you learn by doing!

I used to develop MFC database applications, and now mainly develop COM objects. I thoroughly enjoy developing with COM, and have found two amazing authors that have opened up this technology to me. One is David Chappell, who authored Understanding ActiveX and OLE (Microsoft Press, ISBN-1-57231-216-5). The other, referred to in some circles as “Mr. COM,” is Don Box. Don authored Essential COM (Addison-Wesley, ISBN-0-201-63446-5), which goes beyond David Chappell’s book. Both of these books convey the complex subject of COM in a simple and effective manner. My hat is off to both of these gentlemen! Any serious COM developer should have both of these books on his or her shelf.

Summary

So you want to do COM. What you will find after reading this chapter is that you have only a cursory knowledge of a very deep subject. I’ve covered COM in only an introductory fashion, and COM is a technology that is amazingly simple, yet infinitely difficult! (Arnold Palmer once used this statement to define the game of golf. Fitting, don’t you think?)

Start by creating a simple COM application with one interface and a few methods. When you’ve conquered this, move on to more demanding exercises such as containers and servers, and then into DCOM. Pretty soon, you will be developing ActiveX components, and wondering why you hadn’t done it sooner. Good luck—and COM rules!